/*******************************************************************************
 * Copyright 2010 Ray Ling
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.ring.rinject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.ring.rinject.annotation.RinjectBase;
import org.ring.rinject.annotation.RinjectConcrete;
import org.ring.rinject.exception.AlreadyMappedException;
import org.ring.rinject.exception.NoEnviromentException;
import org.ring.rinject.exception.RinjectException;

public class Rinjector {
	private final RinjectTable table;

	public Rinjector() {
		table = new RinjectTable();
	}

	public Rinjector map(Object obj) throws AlreadyMappedException {

		RinjectConcrete concrete = obj.getClass().getAnnotation(
				RinjectConcrete.class);
		AlreadyMappedException ame = null;

		if (null != concrete) {
			try {
				mapTo(obj, obj.getClass().getSuperclass(), concrete);

				Class<?>[] classes = obj.getClass().getInterfaces();
				for (Class<?> c : classes) {
					mapTo(obj, c, concrete);
				}
			} catch (AlreadyMappedException e) {
				if (null == ame)
					ame = e;
			}
		}

		if (null != ame)
			throw ame;

		return this;
	}

	public Rinjector map(Class<?> type) throws AlreadyMappedException {

		RinjectConcrete concrete = type.getAnnotation(RinjectConcrete.class);
		AlreadyMappedException ame = null;

		if (null != concrete) {
			try {
				mapTo(type, type.getSuperclass(), concrete);

				Class<?>[] classes = type.getInterfaces();
				for (Class<?> c : classes) {
					mapTo(type, c, concrete);
				}
			} catch (AlreadyMappedException e) {
				if (null == ame)
					ame = e;
			}
		} else {
			RinjectBase base = type.getAnnotation(RinjectBase.class);

			if (null != base) {

				Method[] methods = type.getMethods();
				for (Method m : methods) {
					if (Modifier.isStatic(m.getModifiers())) {
						RinjectConcrete rc = m
								.getAnnotation(RinjectConcrete.class);
						if (null != rc) {
							table.set(rc.value(), base.value(), m);
						}
					}
				}
			}
		}

		if (null != ame)
			throw ame;

		return this;
	}

	private void mapTo(Object target, Class<?> superType,
			RinjectConcrete concrete) throws AlreadyMappedException {
		Annotation[] annos = superType.getAnnotations();
		for (Annotation a : annos) {
			if (a instanceof RinjectBase) {
				RinjectBase tryBase = (RinjectBase) a;

				table.set(concrete.value(), tryBase.value(), target);

			}
		}
	}
	
	public RinjectEnvironment env(Object obj)
		throws NoEnviromentException{
		String key = null;
		if(obj instanceof String) key = (String)obj;
		else{
			Class<?> type = null;
			if(obj instanceof Class<?>) type = (Class<?>)obj;
			else type= obj.getClass();
			
			RinjectConcrete concrete = type.getAnnotation(RinjectConcrete.class);
			if(null!=concrete){
				key = concrete.value();
			}else{
				throw new NoEnviromentException("Object[" + obj
						+ "] does not annotated with RinjectConcrete!", obj);
			}
		}
		
		 return table.env(key);
	}
		
	public <R> R findObject(Object env, Object base)
		throws NoEnviromentException{
		return env(env).concrete(base).target();		
	}

	public <R> Class<R> findClass(Object env, Object base)
		throws NoEnviromentException{
		return env(env).concrete(base).targetAsClass();
	}

	public <R> R newObject(Object env, Object base)
		throws RinjectException{
		return env(env).concrete(base).newObject();
	}

	public <R> R callMethod(Object env, Object base, Object... params)
		throws RinjectException{
		return env(env).concrete(base).callMethod(params);
	}
}
